import 'dart:io';

import 'package:dio/dio.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_kit.dart';
import 'package:ffmpeg_kit_flutter/ffmpeg_session.dart';
import 'package:ffmpeg_kit_flutter/ffprobe_kit.dart';
import 'package:ffmpeg_kit_flutter/log.dart';
import 'package:ffmpeg_kit_flutter/return_code.dart';
import 'package:ffmpeg_kit_flutter/statistics.dart';
import 'package:flutter_download_plugin/utils/video/hls_segment.dart';
import 'package:flutter_download_plugin/utils/video/m3u8_label.dart';
import 'package:flutter_download_plugin/utils/video/m3u8_label_utils.dart';
import 'package:flutter_download_plugin/utils/video/video_prevew_info.dart';
import 'package:flutter_hls_parser/flutter_hls_parser.dart';

class M3u8Utils {
  ///根据url获取m3u8的基础信息
  ///[onMasterSelect] 如果有媒体分辨率信息的话可以提供给用户选择,如果没有的话可能并不会回调此方法
  static Future<VideoPreviewInfo> getM3u8InfoByUrl({
    required String url,
    segmentsIndex = 0,
    height = 0,
    width = 0,
    bitrate = 0,
    List<Uri?> mediaPlaylistUrls = const [],
    required Future<int> Function(List<Variant> variants) onMasterSelect,
  }) async {
    Uri uri = Uri.parse(url);
    final Dio _dio = Dio();

    final dioRes = await _dio.get(url);
    String resStr = dioRes.data;

    HlsPlaylist? playList;
    try {
      playList = await HlsPlaylistParser.create().parseString(uri, resStr);
    } on ParserException catch (e) {
      print(e);
    }
    if (playList == null) {
      return VideoPreviewInfo(
        url: url,
        height: 0,
        width: 0,
        bitrate: 0,
        duration: 0,
      );
    }

    if (playList is HlsMasterPlaylist) {
      // master m3u8 file
      // print(playList.variants);
      if (playList.variants.isNotEmpty) {
        int i = await onMasterSelect(playList.variants);
        Variant variant = playList.variants[i];
        //这里后期优化可以根据分辨率的情况取最小的一个分辨率链接方便节约流量提高速度
        return getM3u8InfoByUrl(
            url: variant.url.toString(),
            width: variant.format.width,
            height: variant.format.height,
            bitrate: variant.format.bitrate,
            segmentsIndex: i,
            onMasterSelect: onMasterSelect,
            mediaPlaylistUrls: playList.mediaPlaylistUrls);
      }
      return VideoPreviewInfo(
        url: url,
        height: 0,
        width: 0,
        bitrate: 0,
        duration: 0,
        mediaPlaylistUrls: mediaPlaylistUrls,
      );
    } else if (playList is HlsMediaPlaylist) {
      // media m3u8 file
      //时长
      int duration = 0;
      if (playList.durationUs != null) duration = (playList.durationUs! / 1000 / 1000).toInt();

      // String? downloadUrl;
      // if (playList.segments.isNotEmpty) {
      //   //从前面N个种选出最短的一个
      //   if (playList.segments.length > 10) {
      //     downloadUrl = playList.segments[10].url;
      //   } else {
      //     downloadUrl = playList.segments[0].url;
      //   }
      // }
      // downloadUrl = parserDownloadUrl(url: downloadUrl!, playList: playList);

      List<HlsSegment> segmentsList = convertSegments(playList.segments);
      segmentsList = segmentsList.map((element) {
        // element.url = parserDownloadUrl(url: element.url!, playList: playList!);
        element.url = getM3U8AbsoluteUrl(videoUrl: playList!.baseUri!, line: element.url!);
        return element;
      }).toList();

      return VideoPreviewInfo(
          url: playList.baseUri ?? url,
          height: height,
          width: width,
          bitrate: bitrate,
          duration: duration,
          segmentsIndex: segmentsIndex,
          segments: segmentsList,
          mediaPlaylistUrls: mediaPlaylistUrls,
          m3u8Info: resStr);
    } else {
      return VideoPreviewInfo(
        url: url,
        height: height,
        width: width,
        bitrate: bitrate,
        duration: 0,
        mediaPlaylistUrls: mediaPlaylistUrls,
      );
    }
  }

  ///根据url获取m3u8的码率信息
  ///[onMasterSelect] 如果有媒体分辨率信息的话可以提供给用户选择,如果没有的话可能并不会回调此方法
  static Future<List<Variant>> getM3u8variantListByUrl({
    required String url,
  }) async {
    Uri uri = Uri.parse(url);
    final Dio _dio = Dio();

    final dioRes = await _dio.get(url);
    String resStr = dioRes.data;

    HlsPlaylist? playList;
    try {
      playList = await HlsPlaylistParser.create().parseString(uri, resStr);
    } on ParserException catch (e) {
      print(e);
    }
    if (playList == null) {
      return [];
    }

    if (playList is HlsMasterPlaylist) {
      // master m3u8 file
      // print(playList.variants);
      if (playList.variants.isNotEmpty) {
        return playList.variants;
      } else {
        return [];
      }
    } else {
      return [];
    }
  }

  ///sdk里的segment转换为自定义的hlssegment主要是sdk里的对象的值为final所以自己包装一次
  static List<HlsSegment> convertSegments(List<Segment> segments) {
    List<HlsSegment> list = segments.map((e) => convertSegment(e)).toList();
    return list;
  }

  static HlsSegment convertSegment(Segment segment) {
    HlsSegment hlsSegment = HlsSegment(
        url: segment.url,
        durationUs: segment.durationUs,
        title: segment.title,
        relativeDiscontinuitySequence: segment.relativeDiscontinuitySequence,
        fullSegmentEncryptionKeyUri: segment.fullSegmentEncryptionKeyUri,
        encryptionIV: segment.encryptionIV,
        byterangeOffset: segment.byterangeOffset,
        byterangeLength: segment.byterangeLength,
        hasGapTag: segment.hasGapTag);
    if (segment.initializationSegment != null) {
      hlsSegment.initializationSegment = convertSegment(segment.initializationSegment!);
    }
    return hlsSegment;
  }

  ///下载m3u8文件
  // static Future<void> downloadM3u8({
  //   required String url,
  //   required String fileName,
  // }) async {
  //   VideoPreviewInfo info = await M3u8Utils.getM3u8InfoByUrl(
  //     url: url,
  //     onMasterSelect: (List<Variant> variants) {
  //       return 0;
  //     },
  //   );
  //
  //   print(info);
  // }

  ///得到m3u8文件的绝对网络路径
  static String getM3U8AbsoluteUrl({required String videoUrl, required String line}) {
    if (videoUrl.isEmpty || line.isEmpty) {
      return "";
    }
    if (videoUrl.startsWith("file://") || videoUrl.startsWith("/")) {
      return videoUrl;
    }
    String baseUriPath = getBaseUrl(videoUrl);
    String hostUrl = getHostUrl(videoUrl);
    if (line.startsWith("//")) {
      String tempUrl = getSchema(videoUrl) + ":" + line;
      return tempUrl;
    }
    if (line.startsWith("/")) {
      String pathStr = getPathStr(videoUrl);
      String longestCommonPrefixStr = getLongestCommonPrefixStr(pathStr, line);
      if (hostUrl.endsWith("/")) {
        hostUrl = hostUrl.substring(0, hostUrl.length - 1);
      }
      String tempUrl = hostUrl + longestCommonPrefixStr + line.substring(longestCommonPrefixStr.length);
      return tempUrl;
    }
    if (line.startsWith("http")) {
      return line;
    }
    return baseUriPath + line;
  }

  static String getSchema(String url) {
    if (url.isEmpty) {
      return "";
    }
    int index = url.indexOf("://");
    if (index != -1) {
      String result = url.substring(0, index);
      return result;
    }
    return "";
  }

  /// 例如https://xvideo.d666111.com/xvideo/taohuadao56152307/index.m3u8
  /// 我们希望得到https://xvideo.d666111.com/xvideo/taohuadao56152307/
  ///
  /// @param url
  /// @return
  static String getBaseUrl(String url) {
    if (url.isEmpty) {
      return "";
    }
    int slashIndex = url.lastIndexOf("/");
    if (slashIndex != -1) {
      return url.substring(0, slashIndex + 1);
    }
    return url;
  }

  /// 例如https://xvideo.d666111.com/video/taohuadao56152307/index.m3u8
  /// 我们希望得到https://xvideo.d666111.com/
  ///
  /// @param url
  /// @return
  static String getHostUrl(String url) {
    if (url.isEmpty) {
      return '';
    }
    try {
      final uri = Uri.parse(url); // 解析 URL
      final host = uri.host; // 获取主机名

      if (host.isEmpty) {
        return url; // 如果主机名为空，直接返回原始 URL
      }

      // 获取主机名在 URL 中的起始位置
      final hostIndex = url.indexOf(host);
      if (hostIndex != -1) {
        final port = uri.port; // 获取端口号
        String resultUrl;
        if (port != 0 && port != -1) {
          // 包含端口号
          resultUrl = "${url.substring(0, hostIndex + host.length)}:$port/";
        } else {
          // 不包含端口号
          resultUrl = "${url.substring(0, hostIndex + host.length)}/";
        }
        return resultUrl;
      }
      return url;
    } catch (e) {
      return url; // 解析失败时返回原始 URL
    }
  }

  /// 例如https://xvideo.d666111.com/xvideo/taohuadao56152307/index.m3u8
  /// 我们希望得到   /xvideo/taohuadao56152307/index.m3u8
  ///
  /// @param url
  /// @return
  static String getPathStr(String url) {
    if (url.isEmpty) {
      return "";
    }
    String hostUrl = getHostUrl(url);
    if (hostUrl.isEmpty) {
      return url;
    }
    return url.substring(hostUrl.length - 1);
  }

  /// 获取两个字符串的最长公共前缀
  /// /xvideo/taohuadao56152307/500kb/hls/index.m3u8   与     /xvideo/taohuadao56152307/index.m3u8
  /// <p>
  /// /xvideo/taohuadao56152307/500kb/hls/jNd4fapZ.ts  与     /xvideo/taohuadao56152307/500kb/hls/index.m3u8
  ///
  /// @param str1
  /// @param str2
  /// @return
  static String getLongestCommonPrefixStr(String str1, String str2) {
    if (str1.isEmpty || str2.isEmpty) {
      return '';
    }
    if (str1 == str2) {
      return str1;
    }

    int j = 0;
    while (j < str1.length && j < str2.length) {
      if (str1[j] != str2[j]) {
        break;
      }
      j++;
    }
    return str1.substring(0, j);
  }

  ///根据本地的m3u8分片文件生成本地可以访问的m3u8文件[m3u8FileDir]分片的文件夹，注意文件夹内不要含有非分片文件的其他文件否则会异常
  static void generateLocalM3u8File({
    required String m3u8FileDir,
    required int durationSeconds,
  }) {
    Directory m3u8Dir = Directory(m3u8FileDir);
    if (!m3u8Dir.existsSync()) {
      // throw Exception("Directory does not exist: $m3u8Dir");
      return;
    }

    String fileLocalName = "local.m3u8";
    String fileLocalPath = "$m3u8FileDir/$fileLocalName";

    ///读取目录下的所有文件列表
    List<FileSystemEntity> list = m3u8Dir.listSync();

    ///去除m3u8后缀的文件
    list.removeWhere((element) => element.path.endsWith(".m3u8"));
    list.removeWhere((element) => element.path.endsWith(".txt"));

    //按照文件名称排序
    list.sort((a, b) {
      String aName = a.path.substring(a.path.lastIndexOf("/") + 1);
      String bName = b.path.substring(b.path.lastIndexOf("/") + 1);

      ///提取出文件中的数字部分进行比较
      int aNum = int.parse(aName.replaceAll(RegExp(r'\D'), ''));
      int bNum = int.parse(bName.replaceAll(RegExp(r'\D'), ''));
      return aNum.compareTo(bNum);
    });

    if (list.isEmpty) {
      // throw Exception("No .ts files found in directory");
      return;
    }

    ///读取m3u8文件的内容
    File file = File("$m3u8FileDir/remote.m3u8");
    if (!file.existsSync()) {
      return;
    }

    String content = file.readAsStringSync();
    content = M3u8LabelUtils.addOrUpdateM3u8Label(content, M3u8Label.EXT_MAP, fileLocalPath);

    ///按照换行符分割成列表
    List<String> lines = content.split('\n');

    int segmentIndex = M3u8LabelUtils.findEXTINFIndex(lines);
    int fileIndex = 0;
    //把每个分片的内容替换为访问路径
    for (int i = segmentIndex; i < lines.length; i++) {
      if (lines[i].startsWith(M3u8Label.EXT_INFO)) {
        continue;
      }
      if (lines[i].startsWith(M3u8Label.EXT_END)) {
        //结束标签不处理
        break;
      }
      lines[i] = list[fileIndex++].path;
    }
    //转换为字符串
    String result = lines.join('\n');
    //存为本地文件
    File(fileLocalPath).writeAsStringSync(result);

    // Get.dialog(BrowserVideoPreview(BrowserResourceItem(url: fileLocalPath)), barrierColor: Colors.black87);
  }

  ///合成mp4文件
  ///[deleteSegmentsFiles]合并完成后是否删除分片文件
  static Future<void> mergeMp4Files({
    required String outputFilePath,
    required String dir,
    required bool deleteSegmentsFiles,
  }) async {
    Directory m3u8Dir = Directory(dir);
    if (!m3u8Dir.existsSync()) {
      // throw Exception("Directory does not exist: $m3u8Dir");
      return;
    }

    ///读取目录下的所有文件列表
    List<FileSystemEntity> list = m3u8Dir.listSync();

    ///去除m3u8后缀的文件
    list.removeWhere((element) => element.path.endsWith(".m3u8"));
    list.removeWhere((element) => element.path.endsWith(".txt"));

    //按照文件名称排序
    list.sort((a, b) {
      String aName = a.path.substring(a.path.lastIndexOf("/") + 1);
      String bName = b.path.substring(b.path.lastIndexOf("/") + 1);

      ///提取出文件中的数字部分进行比较
      int aNum = int.parse(aName.replaceAll(RegExp(r'\D'), ''));
      int bNum = int.parse(bName.replaceAll(RegExp(r'\D'), ''));
      return aNum.compareTo(bNum);
    });

    if (list.isEmpty) {
      // throw Exception("No .ts files found in directory");
      return;
    }

    // 获取应用的缓存目录
    final fileListPath = '${m3u8Dir.path}/file_list.txt';
    // String outputFilePath = '${m3u8Dir.path}/output.mp4';

    // 创建 file_list.txt
    final fileList = File(fileListPath);
    final fileSink = fileList.openWrite();
    for (var file in list) {
      // fileSink.writeln("file '${path.basename(file.path)}'");
      fileSink.writeln("file '${file.path}'");
    }
    await fileSink.close();

    // FFmpeg 合成命令
    final List<String> command = [
      '-f',
      'concat',
      '-safe',
      '0',
      '-i',
      fileListPath,
      '-c',
      'copy',
      outputFilePath,
    ];

    FFmpegSession session = await FFmpegKit.executeWithArguments(command);
    //
    final returnCode = await session.getReturnCode();
    print(returnCode);
    if (ReturnCode.isSuccess(returnCode) && deleteSegmentsFiles) {
      ///删除啊所有文件
      list.forEach((element) {
        element.deleteSync();
      });
    }
  }
}
